﻿using System;
using DotNetNuke.Services.FileSystem;

namespace BrandonHaynes.Providers.AmazonS3
	{
	/// <summary>
	/// A class representing a S3 folder.  Note that S3 buckets are arranged under a subdirectory
	/// identified by the RootPath constant (e.g., the bucket named "MyBucket" will be located in the 
	/// path "/AmazonBuckets/MyBucket").
	/// </summary>
	public class S3Folder
		{
		// Identifies our container folder for the S3 buckets; should probably be configurable
		private const string RootPath = "AmazonBuckets/";
		// The second-high bit on a 32-bit integer is used it mark S3 folders.
		//   This implies that the provider will violently explode after the 2^30th = 1,073,741,824 folder
		// is persisted on any installation (this includes folders that are stored and later deleted).
		//   Note also that the DotNetNuke core chokes when provided with a negative identifier, so
		// we are out of luck using the high bit.  Indeed, this bit must be explicitly cleared when
		// generating identifiers for our S3 folders.
		private const int S3FolderIdentifyingBit = 0x40000000;
		// A value for the S3 "root" folder
		private static int S3RootId = MakeId(string.Empty);

		/// <summary>
		/// Gets a folder object representing the S3 "root" folder for a given portal
		/// </summary>
		internal static S3Folder GetRootFolder(int portalId)
			{ return new S3Folder(portalId, string.Empty); }

		#region Internal "Make" Helper Methods

		/// <summary>
		/// Given a folder name, creates a qualified path for that folder (by prepending the root folder)
		/// </summary>
		internal static string MakePath(string name)
			{ return !string.IsNullOrEmpty(name) ? string.Concat(RootPath, name, '/') : RootPath; }

		/// <summary>
		/// Generates a S3 identifier by utilizing the hashcode of the folder name, 
		/// clearing the high-order bit and setting the identifying bit.
		/// 
		/// Note that we are using string.GetHashCode() here, which does not provide full
		/// assurance of even distribution, and we are sure to experience collisions in our
		/// 2^30 space.  We could use the low-30 of a HMAC (e.g., SHA-1) for additional entropy,
		/// but this would also introduce additional computational overhead.  Here, the hash code
		/// seems an appropriate blend of efficiency and distribution.
		/// 
		/// If you happen to collide, which is expected occur with exceeding rarity, change your
		/// S3 bucket name.  We otherwise assume no collisions.
		/// </summary>
		/// <param name="name">The name of the folder for which to generate an identifier</param>
		/// <returns>An integer identifier for the folder with low probability of collision</returns>
		internal static int MakeId(string name)
			{ return (name.GetHashCode() & 0x7FFFFFFF) | S3FolderIdentifyingBit; }

		/// <summary>
		/// Given a folder path, extracts the name of the bucket
		/// </summary>
		internal static string MakeBucketName(string folderPath)
			{ return IsS3Folder(folderPath) ? folderPath.Replace(RootPath, string.Empty).TrimEnd('/') : null; }

		#endregion

		#region Folder Evaluation Helpers

		/// <summary>
		/// Determines if a given path is an S3 folder, or a "normal" folder.  This is effectuated
		/// by looking for the RootPath constant at the beginning of the path.
		/// </summary>
		public static bool IsS3Folder(string path)
			{ return path.StartsWith(RootPath, StringComparison.OrdinalIgnoreCase); }

		/// <summary>
		/// Given a folder identifier, determines if this is a S3-enabled folder.  This is effectuated
		/// by looking for the S3-identifying bit.  Note that the S3 root folder is NOT identified by
		/// this method (see IsS3FolderOrRoot).
		/// </summary>
		public static bool IsS3Folder(int folderId)
			{ return IsS3FolderOrRoot(folderId) && !IsS3Root(folderId); }

		/// <summary>
		/// Given a folder identifier, determines if this is a S3-enabled folder OR the S3 root folder.  
		/// This is effectuated by looking for the S3-identifying bit.  
		/// </summary>
		public static bool IsS3FolderOrRoot(int folderId)
			{ return (folderId & S3FolderIdentifyingBit) != 0; }

		/// <summary>
		/// Given a folder identifier, determines if this is the S3 root folder.  This is effectuated
		/// by looking for the S3-identifying bit.  
		/// </summary>
		public static bool IsS3Root(int folderId)
			{ return folderId == S3RootId; }

		#endregion

		public S3Folder(int portalId, string bucketName)
			{ 
			PortalID = portalId;
			BucketName = bucketName ?? string.Empty;
			}

		public S3Folder(int portalId, string bucketName, DateTime creationDate)
			: this(portalId, bucketName)
			{ CreatedDate = LastUpdated = LastModifiedDate = creationDate; }


		private int? folderId;
		public int FolderID
			{
			get
				{
				if (folderId == null)
					folderId = MakeId(BucketName);

				return folderId.Value;
				}
			}
		public int PortalID { get; private set; }
		public string BucketName { get; private set; }
		public DateTime LastUpdated { get; set; }
		public DateTime CreatedDate { get; set; }
		public DateTime LastModifiedDate { get; set; }

		public string FolderPath { get { return MakePath(BucketName); } }
		public FolderController.StorageLocationTypes StorageLocation { get { return FolderController.StorageLocationTypes.DatabaseSecure; } }
		public bool IsProtected { get { return true; } }
		public bool IsCached { get { return false; } }
		public int CreatedByUserID { get { return -1; } }
		public int LastModifiedByUserID { get { return -1; } }
		}
	}
